package net.aiion.weave.impl.cards.xml;

import java.io.ByteArrayOutputStream;

import net.aiion.weave.asm.Label;
import net.aiion.weave.asm.Opcodes;
import net.aiion.weave.asm.Type;
import net.aiion.weave.spi.bytecode.BytecodeGenerator;
import net.aiion.weave.spi.cards.CardContext;
import net.aiion.weave.spi.cards.CardXmlNames;
import net.aiion.weave.spi.cards.events.CardEvent;
import net.aiion.weave.spi.cards.expressions.CardExpressionResolverService;
import net.aiion.weave.spi.naming.Context;
import net.aiion.weave.spi.painters.PaintBrush;
import net.aiion.weave.spi.painters.PainterService;
import net.aiion.weave.spi.painters.stream.StreamPaintBrush;
import net.aiion.weave.spi.xml.XmlContext;
import net.aiion.weave.spi.xml.XmlGenerator;
import net.aiion.weave.spi.xml.XmlSink;
import net.aiion.weave.spi.xml.events.CharactersXmlEvent;
import net.aiion.weave.spi.xml.events.ElementEndXmlEvent;
import net.aiion.weave.spi.xml.events.ElementStartXmlEvent;
import net.aiion.weave.spi.xml.events.XmlEvent;

public class ComponentTransformation implements XmlSink, Opcodes
{
    private static final String PROP_INTERNAL_NAME = "internalName";

    private static final int INDEX_CARD_CONTEXT = 1;
    private static final int INDEX_STREAM_PAINT_BRUSH = 2;

    private static final String PAINT_BRUSH_INTERNAL_NAME = Type.getInternalName(PaintBrush.class);

    private static final String XML_GENERATOR_FIELD_NAME = "xmlGenerator$ComponentXmlSink";

    private static final String COMPONENT_ATTRIBUTE_VALUE_NAME = "value";

    private BytecodeGenerator bytecodeGenerator;

    public ComponentTransformation() {
    }

    @Override
    public void handleXmlEvent(final XmlContext aXmlContext) {
        bytecodeGenerator = aXmlContext.getProperty("bytecodeGenerator", BytecodeGenerator.class);
        final XmlEvent xmlEvent = aXmlContext.getXmlEvent();
        switch (xmlEvent.getType()) {
        case CardEvent.CARD_CLASS_START:
            bytecodeGenerator.startField(Opcodes.ACC_PRIVATE, XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
            bytecodeGenerator.endField();
            aXmlContext.forwardXmlEvent(xmlEvent);
            break;
        case CardEvent.CARD_PROCESS_CARD_METHOD_START:
            bytecodeGenerator.loadReference(0);
            bytecodeGenerator.loadReference(INDEX_CARD_CONTEXT);
            bytecodeGenerator.invokeInterface(CardContext.class, "getXmlGenerator", XmlGenerator.class);
            bytecodeGenerator.storeField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
                XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
            aXmlContext.forwardXmlEvent(xmlEvent);
            break;
        case XmlEvent.START_ELEMENT:
            handleElementStartXmlEvent(aXmlContext, xmlEvent.asElementStart());
            break;
        case XmlEvent.END_ELEMENT:
            handleElementEndXmlEvent(aXmlContext, xmlEvent.asElementEnd());
            break;
        default:
            aXmlContext.forwardXmlEvent(xmlEvent);
            break;
        }
    }

    private void handleElementStartXmlEvent(final XmlContext aXmlContext,
        final ElementStartXmlEvent aElementStartXmlEvent)
    {
        if (!CardXmlNames.COMPONENT.equals(aElementStartXmlEvent.getName())) {
            aXmlContext.forwardXmlEvent(aElementStartXmlEvent);
            return;
        }

        final Label noStreamPaintBrushLabel = new Label();

        // (PainterService) <<INDEX_CARD_CONTEXT>>.getContext().lookup(PainterService.class)
        bytecodeGenerator.loadReference(INDEX_CARD_CONTEXT);
        bytecodeGenerator.invokeInterface(CardContext.class, "getContext", Context.class);
        bytecodeGenerator.pushConstant(PainterService.class);
        bytecodeGenerator.invokeInterface(Context.class, "lookup", Object.class, Class.class);
        bytecodeGenerator.castChecked(PainterService.class);

        // <<INDEX_STREAM_PAINT_BRUSH>> =
        // .paintComponent(<<INDEX_CARD_CONTEXT>>.getExpressionResolverService().readExpression(<<component-attr-value>>))
        bytecodeGenerator.loadReference(INDEX_CARD_CONTEXT);
        bytecodeGenerator.invokeInterface(CardContext.class, "getExpressionResolverService",
            CardExpressionResolverService.class);
        bytecodeGenerator.pushConstant(aElementStartXmlEvent.getAttributeValue(COMPONENT_ATTRIBUTE_VALUE_NAME));
        bytecodeGenerator.invokeInterface(CardExpressionResolverService.class, "readExpression", Object.class,
            String.class);
        bytecodeGenerator.invokeInterface(PainterService.class, "paintComponent", PaintBrush.class, Object.class);
        bytecodeGenerator.storeReference(INDEX_STREAM_PAINT_BRUSH);

        // <<INDEX_STREAM_PAINT_BRUSH>> instanceof StreamPaintBrush
        // IFEQ <<noStreamPaintBrushLabel>>
        bytecodeGenerator.loadReference(INDEX_STREAM_PAINT_BRUSH);
        bytecodeGenerator.instanceOf(StreamPaintBrush.class);
        bytecodeGenerator.jumpEquals(noStreamPaintBrushLabel);

        // <<INDEX_XML_GENERATOR>>.writeCharacters(((StreamPaintBrush)
        // <<INDEX_STREAM_PAINT_BRUSH>>).getOutputStream().toString(),
        // CharactersXmlEvent.TYPE_RAW_CHARACTER_DATA)
        bytecodeGenerator.loadReference(0);
        bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
            XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
        bytecodeGenerator.loadReference(INDEX_STREAM_PAINT_BRUSH);
        bytecodeGenerator.castChecked(StreamPaintBrush.class);
        bytecodeGenerator.invokeInterface(StreamPaintBrush.class, "getOutputStream", ByteArrayOutputStream.class);
        bytecodeGenerator.invokeVirtual(ByteArrayOutputStream.class, "toString", String.class);
        bytecodeGenerator.loadStatic(CharactersXmlEvent.class, "TYPE_RAW_CHARACTER_DATA", int.class);
        bytecodeGenerator.invokeVirtual(XmlGenerator.class, "writeCharacters", void.class, String.class, int.class);

        bytecodeGenerator.markLabel(noStreamPaintBrushLabel);
        bytecodeGenerator.markFrameAppend(1, new Object[] {
            PAINT_BRUSH_INTERNAL_NAME
        }, 0, null);
    }

    private void handleElementEndXmlEvent(final XmlContext aXmlContext, final ElementEndXmlEvent aElementEndXmlEvent) {
        if (!CardXmlNames.COMPONENT.equals(aElementEndXmlEvent.getName())) {
            aXmlContext.forwardXmlEvent(aElementEndXmlEvent);
            return;
        }
    }
}
